package edu.kit.aifb.ldstream.learner.tasks;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;

import org.openrdf.model.BNode;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.rio.ParserConfig;
import org.openrdf.rio.RDFParser.DatatypeHandling;

import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.PropertyDistributionResult;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.stat.util.IDistributionChecker;

public class PropertyDistributionTask extends LearnerTask{

	private boolean inferencing;
	private Map<Type, HashSet<Instance>> classMap;
	private Map<Type, HashSet<PropertyDistributionResult>> resultSet;
	private IDistributionChecker distribChecker;
	
	public PropertyDistributionTask(Map<Type, HashSet<Instance>> classMap, 
									Map<Type, HashSet<PropertyDistributionResult>> resultSet,
									boolean inferencing, IDistributionChecker distribChecker) {
		this.classMap = classMap;
		this.resultSet = resultSet;
		this.inferencing = inferencing;
		this.distribChecker = distribChecker;
	}
		
	@Override
	public void run() {
		startup();
		setPreserveBNodes();
		computePropertyDistribution();
		shutdown();
	}

	private void computePropertyDistribution(){
		// the number of all properties is needed for the weight computation
		int count;
		for(Type t : this.classMap.keySet()){
			// only crawl classes with instances
			if(!this.classMap.get(t).isEmpty()){
				Map<URI, LinkedList<Integer>> propCountMap = new HashMap<URI, LinkedList<Integer>>();
				this.resultSet.put(t, new HashSet<PropertyDistributionResult>());
				for(Instance instance : this.classMap.get(t)){

					RepositoryResult<Statement> result = null;
					Statement s = null;
					URI pred;
					Value obj;
					try {
						result = this.con.getStatements(instance.getURI(), null, null, this.inferencing);
						try{
							while(result.hasNext()){
								s = result.next();
								pred = s.getPredicate();
								obj = s.getObject();
								if(!propCountMap.containsKey(pred)){
									propCountMap.put(pred, new LinkedList<Integer>());
									propCountMap.get(pred).add(0);
								}
								count = propCountMap.get(pred).getLast();
								propCountMap.get(pred).set(propCountMap.get(pred).size() - 1, count + 1);
								if(obj instanceof BNode){
									computePropertyDistribution((BNode)obj, propCountMap);
								}
							}
						}finally{
							result.close();
						}
					} catch (RepositoryException e) {
						e.printStackTrace();
					}
					// the instance is crawled add a new occurrence value to the list for the next instance
					for(URI uri : propCountMap.keySet()){
						// only add new occurrence if the number of instances is not already reached
						if(propCountMap.get(uri).size() < this.classMap.get(t).size()){
							propCountMap.get(uri).add(0);
						}
					}
					
				}
				// every instance is crawled
				int i;
				for(URI uri : propCountMap.keySet()){
					// creating an array for the distribution check, it needs the length of numInstances
					// because occurrence of 0 needs to be regarded too
					Integer[] values = new Integer[this.classMap.get(t).size()];
					i = 0;
					for(Integer oc : propCountMap.get(uri)){
						values[i++] = oc;
					}
					for(; i < values.length; i++){
						values[i] = new Integer(0);
					}
					this.resultSet.get(t).add(new PropertyDistributionResult(t, uri, this.distribChecker.discrete(values)));
				}
			}
		}
	}

	private void computePropertyDistribution(BNode b, Map<URI, LinkedList<Integer>> propCountMap){

		RepositoryResult<Statement> result = null;
		Statement s = null;
		URI pred;
		Value obj;
		int count;
		try {
			result = this.con.getStatements(b, null, null, this.inferencing);
			try{
				while(result.hasNext()){
					s = result.next();
					pred = s.getPredicate();
					obj = s.getObject();
					if(!propCountMap.containsKey(pred)){
						propCountMap.put(pred, new LinkedList<Integer>());
						propCountMap.get(pred).add(0);
					}
					count = propCountMap.get(pred).getLast();
					propCountMap.get(pred).set(propCountMap.get(pred).size() - 1, count + 1);
					if(obj instanceof BNode){
						computePropertyDistribution((BNode)obj, propCountMap);
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}
	

	private void setPreserveBNodes(){
		this.con.setParserConfig(new ParserConfig(true, true, true, DatatypeHandling.VERIFY));
	}
}
